<template>
  <el-upload
    v-bind="$attrs"
    v-on="$listeners"
    action=""
    :http-request="httpRequest"
    :auto-upload="selfAutoUpload"
    :on-success="selfOnSuccess"
    :on-change="selfOnChange"
    :on-remove="selfOnRemove"
    :on-preview="selfOnPreview"
    :on-error="selfOnError"
    :before-upload="selfBeforeUpload"
    :file-list="fileList"
    :show-file-list="showFileList"
  >
    <template v-slot:file="{ file }">
      <slot name="file" :file="file"></slot>
    </template>
    <slot name="trigger"></slot>
    <slot></slot>
    <slot name="tip"></slot>
  </el-upload>
</template>
<script>
import { loadRemote } from '@vimi/utils-tool'
import { BASE_URL_LIBS } from '@/common/plugins/constant'
import { deleteOss, uploadOss } from '@/common/api/oss'

const imgResizeUrl = `${BASE_URL_LIBS}/img-resize/img-resize.min.js`

function noop() {}

export default {
  name: 'UploadOssBase',
  components: {},
  props: {
    // 文件夹 prefix
    dir: {
      type: String,
      default: 'upload',
    },

    // 传入字符串，则直接使用该字符串作为文件名，否则
    // 0 -> file.name, 1 -> tuid, 2 -> guid
    fileName: {
      type: [String, Number],
      default: 1,
    },

    // 图片是否压缩
    compress: {
      type: Boolean,
      default: true,
    },
    // 图片压缩参数
    compressOptions: {
      type: Object,
      default() {
        return {
          width: 1920,
          quality: 0.8,
        }
      },
    },
    isDelete: { type: Boolean, default: true },
    // 全部上传完成
    onComplete: { type: Function, default: noop },

    // 有冲突的attrs，需要截胡
    fileList: Array,
    autoUpload: Boolean,
    beforeUpload: { type: Function, default: noop },
    onChange: { type: Function, default: noop },
    onRemove: { type: Function, default: noop },
    onPreview: { type: Function, default: noop },
    onSuccess: { type: Function, default: noop },
    onError: { type: Function, default: noop },
    showFileList: { type: Boolean, default: true },
  },
  data() {
    return {
      hasRemoveAction: false,
      uploadList: [],
      completeResolve: null,
      completeReject: null,
    }
  },
  computed: {
    // 修改 el-upload 的默认值，默认为手动上传
    selfAutoUpload() {
      return this.autoUpload || false
    },

    // 处理目录
    userDir() {
      return this.dir.replace(/(^\/)|(\/$)/g, '')
    },
  },
  watch: {
    fileList: {
      handler: function () {
        this.reset()
      },
      deep: true,
      immediate: true,
    },
  },
  created() {},
  methods: {
    reset() {
      const $el = this.$children[0]
      $el && $el.clearFiles()
      this.uploadList = []
    },

    /**
     * 手动上传文件列表
     * 暴露的API供外部调用
     */
    submit() {
      return new Promise(async (resolve, reject) => {
        this.completeResolve = resolve
        this.completeReject = reject

        // 未有文件改变，没有触发 onChange, onRemove 钩子
        if (!this.uploadList.length && !this.hasRemoveAction) {
          return this.doComplete(this.fileList)
        }

        // 是否全部调用完成（有fileList，然后只删除没添加时的情况）
        if (this.uploadList.every(item => item.status === 'success')) {
          return this.doComplete(this.uploadList)
        }

        this.$children[0].submit()
      })
    },

    /**
     * 自定义上传行为，覆盖默认的上传行为
     * 因为用 el-upload 组件自身的 action，在firefox上会出现第一次为空的bug
     * 一个文件就会有一个上传行为，当多文件上传时，需要自己实现所有文件上传完成的监听
     */
    httpRequest(option) {
      uploadOss({
        file: option.file,
        dir: this.dir,
        type: this.fileName,
        progress: option.onProgress,
      })
        .then(url => {
          const { uid, name } = option.file
          option.onSuccess({ uid, name, url })
        })
        .catch(err => {
          option.onError(err)
        })
    },

    /**
     * 所有提交之前的动作，比如压缩、命名等
     * 不能放在 onChange 钩子内，因为当自动上传时，不会等待onChange的同步、异步时间，会直接上传
     */
    async selfBeforeUpload(file) {
      let processedFile = file

      // 1、压缩
      if (this.compress && /^image/.test(file.type)) {
        try {
          await loadRemote(imgResizeUrl, 'ImgResize')
          const imgResize = new window.ImgResize(file, this.compressOptions)
          processedFile = await imgResize.resizeToFile()
        } catch (e) {} // eslint-disable-line
      }

      // 2、处理外部钩子
      const before = this.beforeUpload(processedFile)
      if (before && before.then) {
        const beforeFile = await before.then()
        const fileType = Object.prototype.toString.call(beforeFile)
        if (fileType === '[object File]' || fileType === '[object Blob]') {
          return beforeFile
        }
        return processedFile
      } else if (before !== false) {
        return processedFile
      } else {
        return false
      }
    },

    /**
     * 文件状态改变时的钩子，添加文件、上传成功和上传失败时都会被调用
     */
    async selfOnChange(file, fileList) {
      // 当文件改变时，处理文件
      if (file.status === 'ready') {
        // 单张图片时，只上传最后一张
        if (!this.showFileList) {
          for (let i = 0; i < this.uploadList.length - 1; i++) {
            this.uploadList[i].status = 'success'
          }
        }
      }

      this.uploadList = fileList
      this.onChange(file, fileList)
    },

    selfOnRemove(file, fileList) {
      this.uploadList = fileList
      this.hasRemoveAction = true
      this.onRemove(file, fileList)
    },

    /**
     * 每一个文件上传完成的回调
     */
    selfOnSuccess(res, file, fileList) {
      file.url = res.url

      this.uploadList = fileList
      this.onSuccess(res, file, fileList)

      if (fileList.every(item => item.status === 'success')) {
        this.doComplete(fileList, file)
      }
    },

    selfOnError(err, file, fileList) {
      this.completeReject && this.completeReject(err)
      this.completeResolve = null
      this.completeReject = null
      this.onError(err, file, fileList)
    },

    /**
     * 预览
     */
    selfOnPreview(file) {
      // 获取所有文件列表
      let list = this.uploadList
      if (!list.length) {
        list = this.fileList || []
      }
      if (!this.showFileList) {
        list = list.slice(-1)
      }

      this.onPreview(file, list)
    },

    /**
     * 全部完成的回调处理
     */
    async doComplete(fileList = []) {
      // showFileList为false时，只回调最后一张图片（之前也处理了只上传最后一张图片）
      const list = this.showFileList ? fileList : fileList.slice(-1)
      const result = list.map(item => ({
        name: item.name,
        status: item.status,
        uid: item.uid,
        url: item.url,
        size: item.size,
        type: item.raw?.type,
      }))
      this.completeResolve && this.completeResolve(result)
      this.completeResolve = null
      this.completeReject = null
      const completeRes = this.onComplete(result)
      this.reset()

      // 删除文件要放在完成的回调之后，保证删除文件不影响正常业务运行
      // 不过保险的做法是放在业务也回调成功后，如果数据保存不成功就删除了，会导致文件也找不到了
      // 1. 默认会删除图片，通过返回 false 来中断删除操作
      // 2. 或返回一个promise，需要返回一个真值来触发删除
      // 3. 批量项目上传的附件不能单独删除
      if (!this.isDelete) {
        return
      }
      if (completeRes === void 0 || completeRes === true) {
        this.deleteFileOss(list)
      } else if (completeRes !== null && completeRes.then) {
        const thenRes = await completeRes.then()
        if (thenRes) this.deleteFileOss(list)
      }
    },

    /**
     * 从OSS上删除已删除的文件
     */
    deleteFileOss(fileList) {
      const deleteFile = (this.fileList || []).reduce((prev, curr) => {
        if (!fileList.some(file => file.url === curr.url)) {
          prev.push(curr.url)
        }
        return prev
      }, [])
      if (!deleteFile.length) {
        return
      }
      deleteOss(deleteFile)
    },
  },
}
</script>
<style lang="scss" scoped>
.single-image {
  width: 146px;
  height: 146px;
  position: absolute;
  top: 0;
  left: 0;
}
</style>
